package vn.tictok.facebook;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.List;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.NameValuePair;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.protocol.BasicHttpContext;
import org.apache.http.protocol.HTTP;
import org.apache.http.protocol.HttpContext;
import org.apache.http.util.EntityUtils;

import vn.tictok.facebook.FacebookConstant;

public class LoginTask {
	/**
	 * Login and grant all permissions if necessary.
	 * @param httpclient
	 * @param email
	 * @param password
	 * @return Application access token if successful, else null.
	 * @throws ClientProtocolException
	 * @throws IOException
	 */
	public static String login(HttpClient httpclient, String email, String password)
			throws ClientProtocolException, IOException {
		HttpContext context = new BasicHttpContext();
		int loginStatusCode = submitLoginInfor(httpclient, email, password, context);
		switch (loginStatusCode) {
		case HttpStatus.SC_MOVED_PERMANENTLY:
		case HttpStatus.SC_MOVED_TEMPORARILY:
		case HttpStatus.SC_SEE_OTHER:
		case HttpStatus.SC_TEMPORARY_REDIRECT:
			// If we get here, email and password may be okie. Let's request application access token.
			String accessTokenPage = requestAccessTokenPage(httpclient, context);
			if (accessTokenPage != null) {
				/* 
				 * We need to find out whether this is the first time user login through our application or not. 
				 * If so, Facebook will ask user for application permissions.
				 */
				String allowText = null;
				int allowTextPosition = -1;
				for (String text : FacebookConstant.ALLOW_TEXTS) {
					allowTextPosition = accessTokenPage.indexOf(text);
					if (allowTextPosition > -1) {
						allowText = text;
						break;
					}
				}
				boolean firstLoginTime = allowTextPosition > -1; 
				if (firstLoginTime) {
					accessTokenPage = grantPermissions(httpclient, context, accessTokenPage, allowText, allowTextPosition);
					if (accessTokenPage != null) {
						String accessToken = parseAccessToken(accessTokenPage);
						if (accessToken == null) {
							/**
							 * We have a problem with non English language (Permissions are granted, but Facebook don't send access code back). 
							 * In this case, we try to get access token again.
							 */
							accessTokenPage = requestAccessTokenPage(httpclient, context);
							if (accessTokenPage != null) {
								accessToken = parseAccessToken(accessTokenPage);
							}
						}
						return accessToken;
					}
				} else if (accessTokenPage.contains("window.location")) {
					/*
					 * If we get here, it means that user has used our application before. Just parse our access code.
					 */
					return parseAccessToken(accessTokenPage);
				}
			}
			break;
		}
		return null;
	}
	
	private static int submitLoginInfor(HttpClient httpclient,
			String email, String password, HttpContext context)
			throws IOException, ClientProtocolException,
			UnsupportedEncodingException {
		HttpGet httpget = new HttpGet(FacebookConstant.LOGIN_PAGE_URL);
		HttpResponse response = httpclient.execute(httpget, context);
		HttpEntity entity = response.getEntity();
		List<NameValuePair> nvps = null;
		if (entity != null) {
			nvps = getLoginFormParams(EntityUtils.toString(entity));
		}
		HttpPost httpost = new HttpPost(FacebookConstant.LOGIN_PAGE_URL);
		nvps.add(new BasicNameValuePair("email", email));
		nvps.add(new BasicNameValuePair("pass", password));
		httpost.setEntity(new UrlEncodedFormEntity(nvps, HTTP.UTF_8));
		response = httpclient.execute(httpost, context);
		entity = response.getEntity();
		if (entity != null) {
			EntityUtils.consume(entity);
		}
		return response.getStatusLine().getStatusCode();
	}
	
	private static List<NameValuePair> getLoginFormParams(String htmlContent) {
		List<NameValuePair> params = null;
		int index = htmlContent.indexOf("id=\"login_form\"");
		if (index > -1) {
			params = extractFormParams(htmlContent, index);
			for (int i = params.size() - 1; i > -1; i--) {
				String name = params.get(i).getName();
				if ("email".equals(name) || "pass".equals(name)) {
					params.remove(i);
				}
			}
		}
		return params;
	}
	
	private static String requestAccessTokenPage(HttpClient httpclient, HttpContext context) throws ClientProtocolException, IOException {
		HttpGet httpget = new HttpGet(FacebookConstant.REQUEST_ACCESS_TOKEN_URL);
		HttpResponse response = httpclient.execute(httpget, context);
		HttpEntity entity = response.getEntity();
		if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
			return entity != null ? EntityUtils.toString(entity) : null;
		}
		return null;
	}
	
	private static String grantPermissions(HttpClient httpclient, HttpContext context, String htmlPage, String allowText, int allowTextPosition)
			throws UnsupportedEncodingException, IOException,
			ClientProtocolException {
		List<NameValuePair> nvps = extractFormParams(htmlPage, allowTextPosition);
		nvps.add(new BasicNameValuePair("grant_clicked", allowText));
		HttpPost httpost = new HttpPost(FacebookConstant.GRANT_PERMISSION_URL);
		httpost.setEntity(new UrlEncodedFormEntity(nvps, HTTP.UTF_8));
		HttpResponse response = httpclient.execute(httpost, context);
		if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
			HttpEntity entity = response.getEntity();
			return entity != null ? EntityUtils.toString(entity) : null;
		}
		return null;
	}

	private static String parseAccessToken(String oAuthResult) {
		int index = oAuthResult.indexOf("#access_token=");
		if (index > -1) {
			index += 14;
			int endIndex = oAuthResult.indexOf("&expires_in=");
			String result = oAuthResult.substring(index, endIndex);
			result = result.replace("\\u0025", "%");
			return result;
		}
		return null;
	}
	
	private static List<NameValuePair> extractFormParams(String htmlContent, int index) {
		List<NameValuePair> params = new ArrayList<NameValuePair>();
		//First, we need to get the form content.
		int endIndex = htmlContent.indexOf("</form>", index);
		htmlContent = htmlContent.substring(0, endIndex);
		int startIndex = htmlContent.lastIndexOf("<form");
		htmlContent = htmlContent.substring(startIndex);
		// Now we have the form, just parse it. I think it's faster with indexOf function. But you can replace it with a XML parser.
		index = 0;
		while (index > -1) {
			index = htmlContent.indexOf("<input type=\"hidden\"", index);
			if (index > -1) {
				index = htmlContent.indexOf("name=\"", index) + 6; // "name=\"".length()
																	// = 6
				endIndex = htmlContent.indexOf("\"", index);
				String key = htmlContent.substring(index, endIndex);
				index = htmlContent.indexOf("value=\"", index) + 7; // "value=\"".length()
																	// = 7
				endIndex = htmlContent.indexOf("\"", index);
				String value = htmlContent.substring(index, endIndex);
				index = endIndex;
				params.add(new BasicNameValuePair(key, value));
			}
		}
		return params;
	}
}
